﻿//-----------------------------------------------------------------------
// <copyright file="PropertyComparer.cs" company="LMT">
//     Copyright (c) by La Minh Thuận.
// </copyright>
// <author>thuanla1985@hotmail.com</author>
//-----------------------------------------------------------------------
namespace LMT.Db.Collections
{
    using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Reflection;
    using LMT.Db;

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PropertyComparer<T> : IComparer<T> where T : new()
    {
        /// <summary>
        /// 
        /// </summary>
        private PropertyDescriptor proDesc;

        /// <summary>
        /// 
        /// </summary>
        private ListSortDirection sortDir;

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyComparer&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="property">The property.</param>
        /// <param name="direction">The direction.</param>
        public PropertyComparer(PropertyDescriptor property, ListSortDirection direction)
        {
            this.proDesc = property;
            this.sortDir = direction;
        }

        /// <summary>
        /// Compares the specified x word.
        /// </summary>
        /// <param name="xWord">The x word.</param>
        /// <param name="yWord">The y word.</param>
        /// <returns></returns>
        public int Compare(T xWord, T yWord)
        {
            // Get property values
            object xValue = GetPropertyValue(xWord, proDesc.Name);
            object yValue = GetPropertyValue(yWord, proDesc.Name);

            // Determine sort order
            if (sortDir == ListSortDirection.Ascending)
            {
                return this.CompareAscending(xValue, yValue);
            }
            else
            {
                return this.CompareDescending(xValue, yValue);
            }
        }

        /// <summary>
        /// Equalses the specified x word.
        /// </summary>
        /// <param name="xWord">The x word.</param>
        /// <param name="yWord">The y word.</param>
        /// <returns></returns>
        public bool Equals(T xWord, T yWord)
        {
            return xWord.Equals(yWord);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public int GetHashCode(T obj)
        {
            return obj.GetHashCode();
        }

        // Compare two property values of any type
        /// <summary>
        /// Compares the ascending.
        /// </summary>
        /// <param name="xValue">The x value.</param>
        /// <param name="yValue">The y value.</param>
        /// <returns></returns>
        private int CompareAscending(object xValue, object yValue)
        {
            int result;

            // If values implement IComparer
            if (xValue is IComparable)
            {
                result = ((IComparable)xValue).CompareTo(yValue);
            }
            else 
            {
                if (xValue == null && yValue != null)
                {
                    result = -1;
                }
                else if (xValue != null && yValue == null)
                {
                    result = 1;
                }
                else if (xValue == null && yValue == null)
                {
                    result = 0;
                }
                else if (xValue.Equals(yValue))
                {
                    // If values don't implement IComparer but are equivalent
                    result = 0;
                }
                else
                {
                    // Values don't implement IComparer and are not equivalent, so compare as string values
                    result = xValue.ToString().CompareTo(yValue.ToString());
                }
            }
            
            // Return result
            return result;
        }

        /// <summary>
        /// Compares the descending.
        /// </summary>
        /// <param name="xValue">The x value.</param>
        /// <param name="yValue">The y value.</param>
        /// <returns></returns>
        private int CompareDescending(object xValue, object yValue)
        {
            // Return result adjusted for ascending or descending sort order ie
            // multiplied by 1 for ascending or -1 for descending
            return this.CompareAscending(xValue, yValue) * -1;
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="property">The property.</param>
        /// <returns></returns>
        private object GetPropertyValue(T value, string property)
        {
            // Get property
            PropertyInfo propertyInfo = value.GetType().GetProperty(property);

            // Return value
            return propertyInfo.GetValue(value, null);
        }
    }
}